import { API_BASE_URL } from '@/constants/config';

export interface ApiEnvelope<T, M = unknown> {
  data?: T;
  message?: string;
  error?: string;
  meta?: M;
}

export class ApiError extends Error {
  status: number;
  payload?: ApiEnvelope<unknown> | null;

  constructor(message: string, status: number, payload?: ApiEnvelope<unknown> | null) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.payload = payload ?? null;
  }
}

interface RequestOptions extends Omit<RequestInit, 'body' | 'signal'> {
  token?: string;
  query?: Record<string, string | number | boolean | undefined | null>;
  body?: RequestInit['body'] | Record<string, unknown> | undefined;
  timeoutMs?: number;
}

const resolveErrorMessage = (payload: ApiEnvelope<unknown> | null, fallback: string) => {
  if (!payload) {
    return fallback;
  }

  if (typeof payload.error === 'string' && payload.error.trim().length > 0) {
    return payload.error.trim();
  }

  if (typeof payload.message === 'string' && payload.message.trim().length > 0) {
    return payload.message.trim();
  }

  return fallback;
};

function buildUrl(path: string, query?: RequestOptions['query']) {
  let url = path.startsWith('http')
    ? path
    : `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`;

  if (query) {
    const params = new URLSearchParams();
    Object.entries(query).forEach(([key, value]) => {
      if (value === undefined || value === null) {
        return;
      }
      params.append(key, String(value));
    });
    const queryString = params.toString();
    if (queryString) {
      url += url.includes('?') ? `&${queryString}` : `?${queryString}`;
    }
  }

  return url;
}

async function executeRequest<T, M = unknown>(
  path: string,
  options: RequestOptions = {},
): Promise<ApiEnvelope<T, M>> {
  const { token, query, headers, body, timeoutMs, ...rest } = options;
  const url = buildUrl(path, query);

  const isJsonPayload = body && !(body instanceof FormData) && typeof body !== 'string';
  const finalHeaders = new Headers(headers ?? {});

  if (token) {
    finalHeaders.set('Authorization', `Bearer ${token}`);
  }

  if (isJsonPayload && !finalHeaders.has('Content-Type')) {
    finalHeaders.set('Content-Type', 'application/json');
  }

  if (!finalHeaders.has('Accept')) {
    finalHeaders.set('Accept', 'application/json');
  }

  let response: Response;
  const controller = new AbortController();
  const timeout =
    typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 15000;
  const timer = setTimeout(() => controller.abort(), timeout);
  try {
    response = await fetch(url, {
      ...rest,
      headers: finalHeaders,
      body: isJsonPayload ? JSON.stringify(body) : body,
      signal: controller.signal,
    });
  } catch (err) {
    if ((err as Error)?.name === 'AbortError') {
      throw new Error('请求超时，请检查网络后重试。');
    }
    throw new Error('无法连接服务器，请稍后重试。');
  } finally {
    clearTimeout(timer);
  }

  let payload: ApiEnvelope<T, M> | null = null;
  try {
    payload = (await response.json()) as ApiEnvelope<T, M>;
  } catch {
    payload = null;
  }

  if (!response.ok) {
    const message = resolveErrorMessage(payload, `请求失败，状态码 ${response.status}。`);
    throw new ApiError(message, response.status, payload ?? undefined);
  }

  return payload ?? {};
}

export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
  const payload = await executeRequest<T>(path, options);

  if (payload.data !== undefined) {
    return payload.data as T;
  }

  return (payload as unknown as T) ?? (undefined as T);
}

export async function apiRequestWithMeta<T, M = unknown>(
  path: string,
  options: RequestOptions = {},
): Promise<{ data: T; meta: M | null }> {
  const payload = await executeRequest<T, M>(path, options);
  const data = (payload.data ?? (undefined as unknown)) as T;
  const meta = (payload.meta ?? null) as M | null;
  return { data, meta };
}

// Upload image (multipart/form-data) to backend
export async function uploadImage(
  file: { uri: string; name?: string; type?: string },
  token: string,
  path = '/uploads/images',
): Promise<{
  asset: {
    id: number;
    original_path: string;
    preview_path: string;
    thumb_path?: string;
  };
  original_url: string;
  preview_url: string;
  thumb_url?: string;
}> {
  const form = new FormData();
  const filename = file.name || file.uri.split('/').pop() || `upload.jpg`;
  const contentType = file.type || 'image/jpeg';
  // @ts-ignore - React Native FormData file shape
  form.append('file', { uri: file.uri, name: filename, type: contentType });

  const url = path.startsWith('http')
    ? path
    : `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`;

  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: form,
  });

  if (!res.ok) {
    let msg = `上传失败，状态码 ${res.status}。`;
    try {
      const j = (await res.json()) as ApiEnvelope<unknown>;
      msg = resolveErrorMessage(j as ApiEnvelope<unknown>, msg);
    } catch {}
    throw new Error(msg);
  }

  const payload = (await res.json()) as ApiEnvelope<any>;
  return (payload.data ?? payload) as any;
}
